/*
 * Copyright (C) 2011, EADS France
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */

package xowl.interpreter.backends.jena;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *
 * @author Laurent WOUTERS
 */
public class JenaRepository extends xowl.interpreter.backends.rdf.RDFRepository implements xowl.interpreter.backends.rdf.RDFChangeListener{
    static final String name = "XOWL Jena";
    
    protected ReasonerProvider provider;
    protected com.hp.hpl.jena.rdf.model.Model jenaM0;
    protected com.hp.hpl.jena.rdf.model.InfModel jenaM1;
    protected com.hp.hpl.jena.rdf.model.InfModel jenaM2;
    protected com.hp.hpl.jena.rdf.model.InfModel jenaM3;
    protected com.hp.hpl.jena.reasoner.Reasoner reasoner1;
    protected com.hp.hpl.jena.reasoner.Reasoner reasoner2;
    protected com.hp.hpl.jena.reasoner.rulesys.GenericRuleReasoner ruleReasoner;
    protected boolean needRebuild;
    protected boolean needRebind;

    protected JenaQueryEngine jenaQueryEngine;
    protected JenaGraphTranslator graphTranslator;
    protected JenaRuleTranslator ruleTranslator;

    protected Map<xowl.lang.rules.Rule, com.hp.hpl.jena.reasoner.rulesys.Rule> rules;
    protected Map<xowl.interpreter.backends.rdf.RDFIRIReference, com.hp.hpl.jena.rdf.model.Resource> dictJenaResources;
    protected Map<xowl.interpreter.backends.rdf.RDFBlankNode, com.hp.hpl.jena.rdf.model.Resource> dictJenaBlanks;
    protected Map<xowl.interpreter.backends.rdf.RDFIRIReference, com.hp.hpl.jena.rdf.model.Property> dictJenaProperties;
    protected Map<xowl.interpreter.backends.rdf.RDFLiteralNode, com.hp.hpl.jena.rdf.model.Literal> dictJenaLiterals;
    protected Map<com.hp.hpl.jena.rdf.model.RDFNode, xowl.interpreter.backends.rdf.RDFNode> dictXOWLNode;

    public JenaGraphTranslator getGraphTranslator() { return graphTranslator; }
    public com.hp.hpl.jena.rdf.model.Model getJenaModel() {
        if (needRebuild)
            rebuild();
        else if (needRebind)
            rebind();
        return jenaM3;
    }

    public JenaRepository(xowl.utils.logging.Logger log, ReasonerProvider provider) {
        super(log);
        this.provider = provider;
        this.reasoner1 = provider.createReasoner();
        this.jenaM0 = com.hp.hpl.jena.rdf.model.ModelFactory.createDefaultModel();
        this.jenaM1 = com.hp.hpl.jena.rdf.model.ModelFactory.createInfModel(reasoner1, jenaM0);
        this.needRebuild = true;
        this.needRebind = false;

        this.rules = new HashMap<xowl.lang.rules.Rule, com.hp.hpl.jena.reasoner.rulesys.Rule>();
        this.dictJenaResources = new HashMap<xowl.interpreter.backends.rdf.RDFIRIReference, com.hp.hpl.jena.rdf.model.Resource>();
        this.dictJenaBlanks = new HashMap<xowl.interpreter.backends.rdf.RDFBlankNode, com.hp.hpl.jena.rdf.model.Resource>();
        this.dictJenaProperties = new HashMap<xowl.interpreter.backends.rdf.RDFIRIReference, com.hp.hpl.jena.rdf.model.Property>();
        this.dictJenaLiterals = new HashMap<xowl.interpreter.backends.rdf.RDFLiteralNode, com.hp.hpl.jena.rdf.model.Literal>();
        this.dictXOWLNode = new HashMap<com.hp.hpl.jena.rdf.model.RDFNode, xowl.interpreter.backends.rdf.RDFNode>();
        this.graphTranslator = new JenaGraphTranslator(this);
        this.jenaQueryEngine = new JenaQueryEngine(this);
        this.ruleTranslator = new JenaRuleTranslator(this);
        this.evaluatorParts.add(jenaQueryEngine);
        this.defaultEvaluator.addPart(jenaQueryEngine);
        this.rdfGraph.addEventHandler(this, 0);
    }

    private void rebind() {
        jenaM1.rebind();
        jenaM2.rebind();
        jenaM3.rebind();
        needRebind = false;
    }
    private void rebuild() {
        List<com.hp.hpl.jena.reasoner.rulesys.Rule> temp = new ArrayList<com.hp.hpl.jena.reasoner.rulesys.Rule>();
        temp.addAll(rules.values());

        ruleReasoner = new com.hp.hpl.jena.reasoner.rulesys.GenericRuleReasoner(temp);
        ruleReasoner.setMode(com.hp.hpl.jena.reasoner.rulesys.GenericRuleReasoner.FORWARD_RETE);
        reasoner2 = provider.createReasoner();

        jenaM2 = com.hp.hpl.jena.rdf.model.ModelFactory.createInfModel(ruleReasoner, jenaM1);
        com.hp.hpl.jena.rdf.model.Model empty = com.hp.hpl.jena.rdf.model.ModelFactory.createDefaultModel();
        com.hp.hpl.jena.rdf.model.Model union = com.hp.hpl.jena.rdf.model.ModelFactory.createUnion(jenaM2, empty);
        jenaM3 = com.hp.hpl.jena.rdf.model.ModelFactory.createInfModel(reasoner2, union);
        needRebuild = false;
        needRebind = false;
    }

    @Override public void onChange(xowl.interpreter.impl.AggregatedEvaluator evaluator, xowl.interpreter.backends.rdf.RDFChange change) {
        com.hp.hpl.jena.rdf.model.Resource subject = translate_subject(change.getTriple().getSubject());
        com.hp.hpl.jena.rdf.model.Property property = translate_property(change.getTriple().getProperty());
        com.hp.hpl.jena.rdf.model.RDFNode object = translate_object(change.getTriple().getObject());
        if (change.isAdd()) jenaM0.add(subject, property, object);
        else jenaM0.remove(subject, property, object);
        needRebind = true;
    }
    @Override public void onChanges(xowl.interpreter.impl.AggregatedEvaluator evaluator, xowl.interpreter.backends.rdf.RDFChangeset changeset) {
        for (xowl.interpreter.backends.rdf.RDFChange change : changeset.getRDFChanges())
            onChange(evaluator, change);
    }
    
    protected com.hp.hpl.jena.rdf.model.Resource translate_subject(xowl.interpreter.backends.rdf.RDFSubjectNode node) {
        if (node.getNodeType() == xowl.interpreter.backends.rdf.RDFNodeType.IRIReference)
            return getJenaResource((xowl.interpreter.backends.rdf.RDFIRIReference)node);
        return getJenaResource((xowl.interpreter.backends.rdf.RDFBlankNode)node);
    }
    protected com.hp.hpl.jena.rdf.model.Property translate_property(xowl.interpreter.backends.rdf.RDFProperty node) {
        return getJenaProperty((xowl.interpreter.backends.rdf.RDFIRIReference)node);
    }
    protected com.hp.hpl.jena.rdf.model.RDFNode translate_object(xowl.interpreter.backends.rdf.RDFNode node) {
        if (node.getNodeType() == xowl.interpreter.backends.rdf.RDFNodeType.IRIReference)
            return getJenaResource((xowl.interpreter.backends.rdf.RDFIRIReference)node);
        else if (node.getNodeType() == xowl.interpreter.backends.rdf.RDFNodeType.Blank || node.getNodeType() == xowl.interpreter.backends.rdf.RDFNodeType.Anonymous)
            return getJenaResource((xowl.interpreter.backends.rdf.RDFBlankNode)node);
        return getJenaLiteral((xowl.interpreter.backends.rdf.RDFLiteralNode)node);
    }

    public com.hp.hpl.jena.rdf.model.Resource getJenaResource(xowl.interpreter.backends.rdf.RDFIRIReference node) {
        if (dictJenaResources.containsKey(node))
            return dictJenaResources.get(node);
        com.hp.hpl.jena.rdf.model.Resource jenaRes = jenaM0.createResource(node.getIRI().getHasValue());
        dictJenaResources.put(node, jenaRes);
        dictXOWLNode.put(jenaRes, node);
        return jenaRes;
    }
    public com.hp.hpl.jena.rdf.model.Resource getJenaResource(xowl.interpreter.backends.rdf.RDFBlankNode node) {
        if (dictJenaBlanks.containsKey(node))
            return dictJenaBlanks.get(node);
        com.hp.hpl.jena.rdf.model.Resource jenaRes = jenaM0.createResource();
        dictJenaBlanks.put(node, jenaRes);
        return jenaRes;
    }
    public com.hp.hpl.jena.rdf.model.Property getJenaProperty(xowl.interpreter.backends.rdf.RDFIRIReference node) {
        if (dictJenaProperties.containsKey(node))
            return dictJenaProperties.get(node);
        com.hp.hpl.jena.rdf.model.Property jenaProp = jenaM0.createProperty(node.getIRI().getHasValue());
        dictJenaProperties.put(node, jenaProp);
        dictJenaResources.put(node, jenaProp);
        dictXOWLNode.put(jenaProp, node);
        return jenaProp;
    }
    public com.hp.hpl.jena.rdf.model.Literal getJenaLiteral(xowl.interpreter.backends.rdf.RDFLiteralNode node) {
        if (dictJenaLiterals.containsKey(node))
            return dictJenaLiterals.get(node);
        String lex = node.getLiteral().getLexicalValue();
        String datatype =  node.getLiteral().getMemberOf().getInterpretationOf().getHasIRI().getHasValue();
        com.hp.hpl.jena.rdf.model.Literal jenaLit = null;
        if (datatype != null) {
            if (datatype.equals(xowl.interpreter.impl.OWLDatatype.xsdString))
                jenaLit = jenaM0.createLiteral(lex);
            else
                jenaLit = jenaM0.createTypedLiteral(lex, datatype);
        } else {
            jenaLit = jenaM0.createLiteral(lex);
        }
        dictJenaLiterals.put(node, jenaLit);
        dictXOWLNode.put(jenaLit, node);
        return jenaLit;
    }
    
    public xowl.interpreter.backends.rdf.RDFNode getRDFNode(com.hp.hpl.jena.rdf.model.RDFNode jenaNode) {
        return dictXOWLNode.get(jenaNode);
    }
    public com.hp.hpl.jena.graph.Node getJenaNode(xowl.interpreter.backends.rdf.RDFNode node) {
        if (node.getNodeType() == xowl.interpreter.backends.rdf.RDFNodeType.IRIReference)
            return dictJenaResources.get((xowl.interpreter.backends.rdf.RDFIRIReference)node).asNode();
        else if (node.getNodeType() == xowl.interpreter.backends.rdf.RDFNodeType.Blank || node.getNodeType() == xowl.interpreter.backends.rdf.RDFNodeType.Anonymous)
            return dictJenaBlanks.get((xowl.interpreter.backends.rdf.RDFBlankNode)node).asNode();
        return getJenaLiteral((xowl.interpreter.backends.rdf.RDFLiteralNode)node).asNode();
    }
    
    @Override public void apply(xowl.interpreter.impl.AggregatedEvaluator evaluator, xowl.interpreter.RuleSet ruleset) {
        for (xowl.lang.rules.Rule rule : ruleset.getRules())
            rules.put(rule, ruleTranslator.translate(evaluator, rule));
        needRebuild = true;
    }
    @Override public void unapply(xowl.interpreter.impl.AggregatedEvaluator evaluator, xowl.interpreter.RuleSet ruleset) {
        for (xowl.lang.rules.Rule rule : ruleset.getRules())
            rules.remove(rule);
        needRebuild = true;
    }
}
